using System;
using System.Collections.Generic;
using System.Reflection;

using TrueCode.DependencyManager.BuildActions.Injection;
using TrueCode.DependencyManager.Interface;


namespace TrueCode.DependencyManager.Services.InjectionPoints
{
  public class MethodsByAttributeProvider : IMethodProvider
  {
    private readonly Type _typeofAttribute;

    public MethodsByAttributeProvider() : this( typeof( InjectHereAttribute ) )
    {
    }

    public MethodsByAttributeProvider( Type typeofAttribute )
    {
      _typeofAttribute = typeofAttribute;
    }

    #region Implementation of IMethodsProvider
    public IEnumerable< MethodInfo > GetMethodsOf( Type type )
    {
      var attributeList = new List< object >();
      var methodList = new List< MethodInfo >();

      foreach( var method in type.GetMethods( BindingFlags.Instance | BindingFlags.Public ) )
      {
        var attributes = method.GetCustomAttributes( _typeofAttribute, false );

        #region Assert that there is not more than one attribute of given type
        if( attributes.Length > 1 )
        {
          var exc = new InvalidOperationException( "Attribute marks method to inject must be applied only once." );
          exc.Data.Add( "Method", method );
          exc.Data.Add( "Typeof attribute", _typeofAttribute );
          throw exc;
        }
        #endregion

        if( attributes.Length == 1 )
        {
          attributeList.Add( attributes[ 0 ] );
          methodList.Add( method );
        }
      }

      foreach( var property in type.GetProperties( BindingFlags.Instance | BindingFlags.Public ) )
      {
        var attributes = property.GetCustomAttributes( _typeofAttribute, true );

        #region Assert that there is not more than one attribute of given type
        if( attributes.Length > 1 )
        {
          var exc = new InvalidOperationException( "Attribute marks method to inject must be applied only once." );
          exc.Data.Add( "Method", property );
          exc.Data.Add( "Typeof attribute", _typeofAttribute );
          throw exc;
        }
        #endregion

        if( attributes.Length == 1 )
        {
          attributeList.Add( attributes[ 0 ] );
          methodList.Add( property.GetSetMethod( true ) );
        }
      }

      var sortedMethods = methodList.ToArray();
      if( methodList.Count > 0 && _typeofAttribute is IComparable )
      {
        var attributeArray = attributeList.ToArray();
        Array.Sort( attributeArray, sortedMethods );
      }

      return sortedMethods;
    }
    #endregion
  }
}